﻿using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using MathExpression;
using NUnit.Framework;
using SimplexSolver;

namespace LPTask.Test
{
    [TestFixture]
    public class ScalarSimplexSolveFixture
    {
        private SolverComposition _solver;

        private bool _incorrectTaskDetected;

        [TestFixtureSetUp]
        public void Initialize()
        {
            _solver = GlobalVariables.SolverComposition;
        }

        [Test]
        public void Solve()
        {
            ILPTask task = CreateTask();
            var result = _solver.SolveScalar(task) as SingleSolution;
            Assert.IsNotNull(result);
            Assert.AreEqual(33.0/7.0, (result.Value.Data as ScalarData).Value, GlobalVariables.EPSILON);
            IMathExpression[] x = result.X;
            Assert.AreEqual(3, x.Length);
            Assert.AreEqual(5.0/14.0, (x[0].Data as ScalarData).Value, GlobalVariables.EPSILON);
            Assert.AreEqual(0.0, (x[1].Data as ScalarData).Value, GlobalVariables.EPSILON);
            Assert.AreEqual(17.0/14.0, (x[2].Data as ScalarData).Value, GlobalVariables.EPSILON);
        }

        [Test]
        public void SolveWithSecondBasis()
        {
            ILPTask task = CraeteSecondBasisMinTask();
            var result = _solver.SolveScalar(task) as SingleSolution;

            Assert.IsNotNull(result);
            Assert.AreEqual(82.0, (result.Value.Data as ScalarData).Value, GlobalVariables.EPSILON);
            IMathExpression[] x = result.X;
            Assert.AreEqual(3, x.Length);
            Assert.AreEqual(0.0, (x[0].Data as ScalarData).Value, GlobalVariables.EPSILON);
            Assert.AreEqual(13.0, (x[1].Data as ScalarData).Value, GlobalVariables.EPSILON);
            Assert.AreEqual(10.0, (x[2].Data as ScalarData).Value, GlobalVariables.EPSILON);
        }

        [Test]
        public void SolveNoSolutionsTask()
        {
            ILPTask task = CreateNoSolutionTask();
            string message = "Не удалось решить задачу методом искусственного базиса";
            Assert.Throws<Exception>(() => _solver.SolveScalar(task), message);
        }

        [Test]
        public void SolveIncorrectTask()
        {
            IEnumerable<ILPTask> tasks = CreateIncorrectTasks();
            _solver.MessageTransferNeeded += Solver_MessageTransferNeeded;
            foreach (ILPTask lpTask in tasks)
            {
                ILPTask task = lpTask;
                _solver.SolveScalar(task);
                Assert.AreEqual(true, _incorrectTaskDetected);
                _incorrectTaskDetected = false;
            }
            _solver.MessageTransferNeeded -= Solver_MessageTransferNeeded;
        }

        [Test]
        public void SolveDoubleSolutionTask()
        {
            ILPTask task = CreateDoubleSolutionTask();
            var solution = _solver.SolveScalar(task) as DoubleSolution;

            Assert.IsNotNull(solution);

            IMathExpression value = solution.Value;
            Assert.AreEqual(5.0, (value.Data as ScalarData).Value, GlobalVariables.EPSILON);

            IMathExpression[] point1 = solution.Point1;
            Assert.AreEqual(2.0, (point1[0].Data as ScalarData).Value, GlobalVariables.EPSILON);
            Assert.AreEqual(3.0, (point1[1].Data as ScalarData).Value, GlobalVariables.EPSILON);

            IMathExpression[] point2 = solution.Point2;
            Assert.AreEqual(0.0, (point2[0].Data as ScalarData).Value, GlobalVariables.EPSILON);
            Assert.AreEqual(5.0, (point2[1].Data as ScalarData).Value, GlobalVariables.EPSILON);
        }

        private ILPTask CreateTask()
        {
            var task = new SimplexSolver.LPTask(3);
            TargetFunction target = CreateTargetMax();
            task.SetTargetFunction(target);
            IEnumerable<Restriction> restrictions = CreateRestrictions();
            foreach (Restriction restriction in restrictions)
            {
                ReadOnlyCollection<IMathExpression> dataRow = restriction.DataRow;
                const Restriction.EIneqMode ineqMode = Restriction.EIneqMode.LessOrEqual;
                IMathExpression limit = restriction.Limit;
                task.AddRestriction(new Restriction(dataRow, ineqMode, limit));
            }
            return task;
        }

        private ILPTask CraeteSecondBasisMinTask()
        {
            var task = new SimplexSolver.LPTask(3);
            TargetFunction target = CreateTargetMin();
            task.SetTargetFunction(target);
            List<Restriction> restrictions = CreateRestrictions();
            for (int i = 0; i < restrictions.Count; i++)
            {
                ReadOnlyCollection<IMathExpression> dataRow = restrictions[i].DataRow;
                Restriction.EIneqMode ineqMode = Restriction.EIneqMode.LessOrEqual;
                if (i == 0)
                {
                    ineqMode = Restriction.EIneqMode.MoreOrEqual;
                }
                if (i == 1)
                {
                    ineqMode = Restriction.EIneqMode.Equal;
                }
                IMathExpression limit = restrictions[i].Limit;
                task.AddRestriction(new Restriction(dataRow, ineqMode, limit));
            }
            return task;
        }

        private ILPTask CreateNoSolutionTask()
        {
            ILPTask task = CraeteSecondBasisMinTask();
            var restrictions = new List<Restriction>(task.Restrictions);
            var dataRow = new List<IMathExpression>(restrictions[0].DataRow);
            dataRow[2] = ExpressionFactory.Create(-3.0);
            restrictions[0] = new Restriction(dataRow, restrictions[0].IneqMode, restrictions[0].Limit);
            var resultTask = new SimplexSolver.LPTask(task.Size);
            resultTask.SetTargetFunction(task.Target);
            foreach (Restriction restriction in restrictions)
            {
                resultTask.AddRestriction(restriction);
            }
            return resultTask;
        }

        private IEnumerable<ILPTask> CreateIncorrectTasks()
        {
            var tasks = new List<ILPTask>
                            {
                                CreateIncorrectTargetTask(),
                                CreateIncorrectFreeValueTask(),
                                CreateIncorrectRestrictionsTask()
                            };
            return tasks;
        }

        private ILPTask CreateIncorrectTargetTask()
        {
            List<Restriction> restrictions = CreateRestrictions();
            TargetFunction target = CreateTargetMax();

            var incorrectTargetCoefs = new List<IMathExpression>(target.Coefs);
            incorrectTargetCoefs[0] = ExpressionFactory.Create(1.0, 1.0);
            var incorrectTargetTask = new SimplexSolver.LPTask(3);
            foreach (Restriction restriction in restrictions)
            {
                incorrectTargetTask.AddRestriction(restriction);
            }
            incorrectTargetTask.SetTargetFunction(new TargetFunction(incorrectTargetCoefs, target.Direction));
            return incorrectTargetTask;
        }

        private ILPTask CreateIncorrectFreeValueTask()
        {
            List<Restriction> restrictions = CreateRestrictions();
            TargetFunction target = CreateTargetMax();
            restrictions[0] = new Restriction(restrictions[0].DataRow, restrictions[0].IneqMode,
                                              ExpressionFactory.Create(1.0, 1.0));
            var task = new SimplexSolver.LPTask(3);
            task.SetTargetFunction(target);
            foreach (Restriction restriction in restrictions)
            {
                task.AddRestriction(restriction);
            }
            return task;
        }

        private ILPTask CreateIncorrectRestrictionsTask()
        {
            List<Restriction> restrictions = CreateRestrictions();
            TargetFunction target = CreateTargetMax();
            var dataRow = new List<IMathExpression>(restrictions[0].DataRow);
            dataRow[0] = ExpressionFactory.Create(1.0, 1.0);
            restrictions[0] = new Restriction(dataRow, restrictions[0].IneqMode, restrictions[0].Limit);
            var task = new SimplexSolver.LPTask(3);
            task.SetTargetFunction(target);
            foreach (Restriction restriction in restrictions)
            {
                task.AddRestriction(restriction);
            }
            return task;
        }

        private TargetFunction CreateTargetMin()
        {
            var targetRow = new List<IMathExpression>
                                {
                                    ExpressionFactory.Create(3.0),
                                    ExpressionFactory.Create(4.0),
                                    ExpressionFactory.Create(3.0)
                                };
            return new TargetFunction(targetRow, TargetFunction.EDirection.Min);
        }

        private TargetFunction CreateTargetMax()
        {
            var targetRow = new List<IMathExpression>
                                {
                                    ExpressionFactory.Create(3.0),
                                    ExpressionFactory.Create(-4.0),
                                    ExpressionFactory.Create(3.0)
                                };
            return new TargetFunction(targetRow, TargetFunction.EDirection.Max);
        }

        private List<Restriction> CreateRestrictions()
        {
            var restrictions = new Restriction[3];
            IList<IEnumerable<IMathExpression>> rows = CreateRestrictionMatrix();
            IList<IMathExpression> freeValues = CreateFreeValues();
            for (int i = 0; i < 3; i++)
            {
                restrictions[i] = new Restriction(rows[i], Restriction.EIneqMode.Equal, freeValues[i]);
            }
            return restrictions.ToList();
        }

        private IList<IMathExpression> CreateFreeValues()
        {
            var freeValues = new List<IMathExpression>
                                 {
                                     ExpressionFactory.Create(4.0),
                                     ExpressionFactory.Create(8.0),
                                     ExpressionFactory.Create(2.0)
                                 };
            return freeValues;
        }

        private IList<IEnumerable<IMathExpression>> CreateRestrictionMatrix()
        {
            var matrix = new List<IEnumerable<IMathExpression>>
                             {
                                 new[]
                                     {
                                         ExpressionFactory.Create(1.0),
                                         ExpressionFactory.Create(-2.0),
                                         ExpressionFactory.Create(3.0)
                                     },
                                 new[]
                                     {
                                         ExpressionFactory.Create(-5.0),
                                         ExpressionFactory.Create(6.0),
                                         ExpressionFactory.Create(-7.0)
                                     },
                                 new[]
                                     {
                                         ExpressionFactory.Create(9.0),
                                         ExpressionFactory.Create(0.0),
                                         ExpressionFactory.Create(-1.0)
                                     }
                             };
            return matrix;
        }

        private ILPTask CreateDoubleSolutionTask()
        {
            var matrix = new List<IMathExpression[]>
                             {
                                 new[]
                                     {
                                         ExpressionFactory.Create(1.0),
                                         ExpressionFactory.Create(0.0),
                                     },
                                 new[]
                                     {
                                         ExpressionFactory.Create(1.0),
                                         ExpressionFactory.Create(1.0),
                                     },
                             };
            var freeValues = new List<IMathExpression>
                                 {
                                     ExpressionFactory.Create(2.0),
                                     ExpressionFactory.Create(5.0)
                                 };
            var restrictions = new List<Restriction>
                                   {
                                       new Restriction(matrix[0], Restriction.EIneqMode.LessOrEqual, freeValues[0]),
                                       new Restriction(matrix[1], Restriction.EIneqMode.LessOrEqual, freeValues[1])
                                   };
            var target = new List<IMathExpression>
                             {
                                 ExpressionFactory.Create(1.0),
                                 ExpressionFactory.Create(1.0),
                             };
            var task = new SimplexSolver.LPTask(2);
            task.AddRestriction(restrictions[0]);
            task.AddRestriction(restrictions[1]);
            task.SetTargetFunction(new TargetFunction(target, TargetFunction.EDirection.Max));
            return task;
        }

        private void Solver_MessageTransferNeeded(object sender, MessageTransferNeededEventArgs e)
        {
            _incorrectTaskDetected = true;
        }
    }
}